/*
 * Copyright 2007 Matt Jensen
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */
package org.jtell.internal;

import java.util.concurrent.atomic.AtomicLong;

/**
 * <p>
 * <code>Period</code> is a utility class for keeping track of time intervals. The length of the interval is configured
 * via the constructor; calls to {@link #elapsed()} will return <code>false</code> <em>except</em> for the first call
 * after the period elapses. When the period elapses, it will return <code>true</code> and the period will start over
 * (with {@link #elapsed()} again returning <code>false</code>.) 
 * </p>
 * <p>
 * <strong>Thread Safety</strong><br/>
 * Instances of this class are safe for multithreaded access. 
 * </p>
 */
public class Period
{
    /**
     * <p>
     * System time of the beginning of the next time interval.
     * </p>
     */
    private final AtomicLong m_nextPeriodStartTimeMillis;

    /**
     * <p>
     * The time period, in milliseconds. 
     * </p>
     */
    private final long m_periodMillis;

    /**
     * <p>
     * Construct a {@link Period} instance.
     * </p>
     *
     * @param periodMillis the time period, in milliseconds.
     */
    public Period(final long periodMillis)
    {
        super();
        if (periodMillis < 1)
        {
            throw new IllegalArgumentException("The [periodMillis] argument must be greater than zero.");
        }

        m_periodMillis = periodMillis;
        m_nextPeriodStartTimeMillis = new AtomicLong(System.currentTimeMillis() + m_periodMillis);
    }

    /**
     * <p>
     * Determine whether the configured period has elapsed. Will return <code>true</code> exactly <em>once</em> per
     * time interval.
     * </p>
     *
     * @return <code>boolean</code> <code>true</code> if the period has elapsed.
     */
    public boolean elapsed()
    {
        // Get the current time and compare it to the next period start time.
        final boolean result;
        final long currentTimeMillis = System.currentTimeMillis();
        final long nextPeriodStartTimeMillis = m_nextPeriodStartTimeMillis.get();
        if (currentTimeMillis < nextPeriodStartTimeMillis)
        {
            // The period has not elapsed.
            result = false;
        }
        else
        {
            // The period has elapsed. Attempt to replace the next period start time value. If we are able to replace
            // it, then this is the first call after the period elapsed and we must return true. Else return false.
            final long newNextPeriodStartTimeMillis = currentTimeMillis + m_periodMillis;
            result = m_nextPeriodStartTimeMillis.compareAndSet(nextPeriodStartTimeMillis, newNextPeriodStartTimeMillis);
        }
        return result;
    }
}
